package com.et114.components.acegi.acl;

import org.acegisecurity.AuthorizationServiceException;
import org.acegisecurity.acl.basic.AclObjectIdentity;
import org.acegisecurity.acl.basic.AclObjectIdentityAware;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.et114.components.acegi.acl.domain.AclDomainBuilder;
//import com.et114.components.acegi.acl.voter.AclDomainClass;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @author guanhw
 */
public class AclUtils {

	// ~ Static fields/initializers
	// =============================================

	protected static final Log	logger	= LogFactory.getLog( AclUtils.class );

	/**
	 * 取得domain对象
	 * 
	 * @param secureObject 拦截的对象，一般是业务对象的方法
	 * @param processDomainObjectClass 需要处理的doaminClass
	 * @return 根据processDomainObjectClass返回domainObject
	 */
	public static Object getDomainObjectInstance ( Object secureObject ,
			Class processDomainObjectClass ) {
		MethodInvocation invocation = ( MethodInvocation ) secureObject;

		// Check if this MethodInvocation provides the required argument
		Method method = invocation.getMethod();
		Class [ ] params = method.getParameterTypes();

		for ( int i = 0; i < params.length; i++ ) {
			if ( processDomainObjectClass.isAssignableFrom( params [ i ] ) ) {
				return invocation.getArguments() [ i ];
			}
		}

		throw new AuthorizationServiceException( "MethodInvocation: "
				+ invocation + " did not provide any argument of type: "
				+ processDomainObjectClass );
	}

	/**
	 * 根据传入的参数构造一个AclDomainBuilder，可以在后期取得AclObjectIdentity，指定domainObject
	 * 首先会看传入的methodDomainObjectClass时候有效，有效则调用该方法取得domainClass。
	 * 无效则察看改方法有无AclDomainClass这个anno，有的话则调用取得doaminClass。 <p/>
	 * 该方法假设拦截方法的参数是一个id，无法取得domainClass时使用。
	 * 
	 * @param secureObject 拦截的对象，一般是业务对象的方法
	 * @param methodDomainObjectClass 取得domainClass的方法，可为空。
	 * @param defaultDomainClass 默认的domainClass
	 * @return 返回一个AclDomainBuilder
	 */
	public static AclDomainBuilder getDomainObjectInstance (
			Object secureObject , String methodDomainObjectClass ,
			Class defaultDomainClass ) {
		MethodInvocation invocation = ( MethodInvocation ) secureObject;

		// Check if this MethodInvocation provides the required argument
		Method method = invocation.getMethod();
		Class [ ] params = method.getParameterTypes();

		Serializable id = null;
		for ( int i = 0; i < params.length; i++ ) {
			if ( Serializable.class.isAssignableFrom( params [ i ] ) ) {
				id = ( Serializable ) invocation.getArguments() [ i ];
				break;
			}
		}

		if ( id == null ) {
			throw new AuthorizationServiceException( "MethodInvocation: "
					+ invocation + " did not provide any ID argument." );
		}
		Class domainClass = defaultDomainClass;
		if ( domainClass == null ) {
			// try to find in method and anno
			if ( StringUtils.isNotBlank( methodDomainObjectClass ) ) {
				Object object = invocation.getThis();
				Class clazz = object.getClass();

				try {
					Method classMethod = clazz.getMethod(
							methodDomainObjectClass, new Class [ ] {} );
					Object result = classMethod.invoke( object,
							new Object [ ] {} );
					domainClass = ( Class ) result;
				} catch ( Exception e ) {
					logger.error( e );
					throw new AuthorizationServiceException(
							"MethodInvocation: "
									+ invocation
									+ " provide wrrong domainClassMethod argument : "
									+ methodDomainObjectClass );
				}
			}
			/*
			 * else if (method.isAnnotationPresent(AclDomainClass.class)) {
			 * domainClass = method.getAnnotation(AclDomainClass.class)
			 * .value(); }
			 */else {
				throw new AuthorizationServiceException( "MethodInvocation: "
						+ invocation
						+ " did not provide any domainClass argument." );
			}
		}

		return new AclDomainBuilder( domainClass, id );
	}

	/**
	 * 由doaminObject构造成AclObjectIdentity,
	 * AclObjectIdentity是跟数据库中的表对应的数据，作用是描述所有的受保护的domainClass
	 * 
	 * @param domainInstance
	 * @param defaultAclObjectIdentityClass
	 */
	public static AclObjectIdentity obtainIdentity ( Object domainInstance ,
			Class defaultAclObjectIdentityClass ) {
		if ( domainInstance instanceof AclObjectIdentityAware ) {
			AclObjectIdentityAware aclObjectIdentityAware = ( AclObjectIdentityAware ) domainInstance;

			if ( logger.isDebugEnabled() ) {
				logger.debug( "domainInstance: " + domainInstance
						+ " cast to AclObjectIdentityAware" );
			}

			return aclObjectIdentityAware.getAclObjectIdentity();
		}

		try {
			Constructor constructor = defaultAclObjectIdentityClass
					.getConstructor( new Class [ ] { Object.class } );

			if ( logger.isDebugEnabled() ) {
				logger.debug( "domainInstance: " + domainInstance
						+ " attempting to pass to constructor: " + constructor );
			}

			return ( AclObjectIdentity ) constructor
					.newInstance( new Object [ ] { domainInstance } );
		} catch ( Exception ex ) {
			if ( logger.isDebugEnabled() ) {
				logger.debug( "Error attempting construction of "
						+ defaultAclObjectIdentityClass + ": "
						+ ex.getMessage(), ex );

				if ( ex.getCause() != null ) {
					logger.debug( "Cause: " + ex.getCause().getMessage(), ex
							.getCause() );
				}
			}

			return null;
		}
	}
}
